// 定义一个延时函数，返回一个Promise，该Promise在指定时间后解决
const wait = ms => new Promise(resolve => setTimeout(resolve, ms))

/**
 * 定义一个防抖函数，用于限制函数的执行频率
 * @param {Function} f 要执行的函数
 * @param {number} wait 等待时间
 * @param {boolean} immediate 是否在第一次调用时立即执行
 * @returns {Function} 返回一个新的防抖函数
 */
const debounce = (f, wait, immediate) => {
    let timeout
    return (...args) => {
        const later = () => {
            timeout = null
            if (!immediate) f(...args)
        }
        const callNow = immediate && !timeout
        if (timeout) clearTimeout(timeout)
        timeout = setTimeout(later, wait)
        if (callNow) f(...args)
    }
}

/**
 * 定义一个线性插值函数
 * @param {number} min 最小值
 * @param {number} max 最大值
 * @param {number} x 插值因子
 * @returns {number} 返回插值结果
 */
const lerp = (min, max, x) => x * (max - min) + min

/**
 * 定义一个 easeOutQuad 动画缓动函数
 * @param {number} x 时间因子
 * @returns {number} 返回缓动结果
 */
const easeOutQuad = x => 1 - (1 - x) * (1 - x)

/**
 * 定义一个动画函数，用于在两个值之间进行动画过渡
 * @param {number} a 起始值
 * @param {number} b 结束值
 * @param {number} duration 动画持续时间
 * @param {Function} ease 缓动函数
 * @param {Function} render 渲染函数
 * @returns {Promise} 返回一个Promise，动画结束时解决
 */
const animate = (a, b, duration, ease, render) => new Promise(resolve => {
    let start
    const step = now => {
        start ??= now
        const fraction = Math.min(1, (now - start) / duration)
        render(lerp(a, b, ease(fraction)))
        if (fraction < 1) requestAnimationFrame(step)
        else resolve()
    }
    requestAnimationFrame(step)
})

// 处理有时（或总是？）不返回客户区矩形的折叠范围
// 尝试获取一个非折叠的范围或元素
const uncollapse = range => {
    if (!range?.collapsed) return range
    const {endOffset, endContainer} = range
    if (endContainer.nodeType === 1) return endContainer
    if (endOffset + 1 < endContainer.length) range.setEnd(endContainer, endOffset + 1)
    else if (endOffset > 1) range.setStart(endContainer, endOffset - 1)
    else return endContainer.parentNode
    return range
}

/**
 * 创建一个范围对象
 * @param {Document} doc 文档对象
 * @param {Node} node 节点对象
 * @param {number} start 起始位置
 * @param {number} end 结束位置，默认为起始位置
 * @returns {Range} 返回一个新的范围对象
 */
const makeRange = (doc, node, start, end = start) => {
    const range = doc.createRange()
    range.setStart(node, start)
    range.setEnd(node, end)
    return range
}

/**
 * 使用二分查找在文本节点中找到一个偏移值
 * @param {Document} doc 文档对象
 * @param {Node} node 文本节点
 * @param {Function} cb 回调函数，用于比较范围
 * @param {number} start 起始位置，默认为0
 * @param {number} end 结束位置，默认为文本节点的长度
 * @returns {number} 返回找到的偏移值
 */
const bisectNode = (doc, node, cb, start = 0, end = node.nodeValue.length) => {
    if (end - start === 1) {
        const result = cb(makeRange(doc, node, start), makeRange(doc, node, end))
        return result < 0 ? start : end
    }
    const mid = Math.floor(start + (end - start) / 2)
    const result = cb(makeRange(doc, node, start, mid), makeRange(doc, node, mid, end))
    return result < 0 ? bisectNode(doc, node, cb, start, mid)
        : result > 0 ? bisectNode(doc, node, cb, mid, end) : mid
}

// 导入 NodeFilter 的常量
const {
    SHOW_ELEMENT, SHOW_TEXT, SHOW_CDATA_SECTION,
    FILTER_ACCEPT, FILTER_REJECT, FILTER_SKIP
} = NodeFilter

// 定义一个过滤器，用于筛选可见的节点
const filter = SHOW_ELEMENT | SHOW_TEXT | SHOW_CDATA_SECTION

/**
 * 获取可见的范围
 * @param {Document} doc 文档对象
 * @param {number} start 起始位置
 * @param {number} end 结束位置
 * @param {Function} mapRect 用于映射矩形的函数
 * @returns {Range} 返回一个新的范围对象，表示可见的范围
 */
const getVisibleRange = (doc, start, end, mapRect) => {
    // 首先获取所有可见的节点
    const acceptNode = node => {
        const name = node.localName?.toLowerCase()
        // 忽略所有的脚本、样式及其子节点
        if (name === 'script' || name === 'style') return FILTER_REJECT
        if (node.nodeType === 1) {
            const {left, right} = mapRect(node.getBoundingClientRect())
            // 如果节点完全不在视口中，则无需检查子节点
            if (right < start || left > end) return FILTER_REJECT
            // 元素必须完全在视口中才能被视为可见
            // 因为不能为元素指定偏移量
            if (left >= start && right <= end) return FILTER_ACCEPT
            // TODO: 应该允许不包含文本的元素
            // 因为它们可能会超出整个视口的范围
            // 尤其是在滚动模式下
        } else {
            // 忽略空的文本节点
            if (!node.nodeValue?.trim()) return FILTER_REJECT
            // 创建范围以获取矩形
            const range = doc.createRange()
            range.selectNodeContents(node)
            const {left, right} = mapRect(range.getBoundingClientRect())
            // 如果节点的任何部分在视口中，则视为可见
            if (right >= start && left <= end) return FILTER_ACCEPT
        }
        return FILTER_SKIP
    }
    if (!doc) return
    const walker = doc.createTreeWalker(doc.body, filter, {acceptNode})
    const nodes = []
    for (let node = walker.nextNode(); node; node = walker.nextNode())
        nodes.push(node)

    // 我们只对第一个和最后一个可见节点感兴趣
    const from = nodes[0] ?? doc.body
    const to = nodes[nodes.length - 1] ?? from

    // 找到可见性变化的偏移量
    const startOffset = from.nodeType === 1 ? 0
        : bisectNode(doc, from, (a, b) => {
            const p = mapRect(a.getBoundingClientRect())
            const q = mapRect(b.getBoundingClientRect())
            if (p.right < start && q.left > start) return 0
            return q.left > start ? -1 : 1
        })
    const endOffset = to.nodeType === 1 ? 0
        : bisectNode(doc, to, (a, b) => {
            const p = mapRect(a.getBoundingClientRect())
            const q = mapRect(b.getBoundingClientRect())
            if (p.right < end && q.left > end) return 0
            return q.left > end ? -1 : 1
        })

    const range = doc.createRange()
    range.setStart(from, startOffset)
    range.setEnd(to, endOffset)
    return range
}

/**
 * 获取文档的书写方向
 * @param {Document} doc 文档对象
 * @returns {Object} 返回一个对象，包含垂直和从右到左的布尔值
 */
const getDirection = doc => {
    const {defaultView} = doc
    const {writingMode, direction} = defaultView.getComputedStyle(doc.body)
    const vertical = writingMode === 'vertical-rl'
        || writingMode === 'vertical-lr'
    const rtl = doc.body.dir === 'rtl'
        || direction === 'rtl'
        || doc.documentElement.dir === 'rtl'
    return {vertical, rtl}
}

/**
 * 获取文档的背景样式
 * @param {Document} doc 文档对象
 * @returns {string} 返回文档的背景样式
 */
const getBackground = doc => {
    const bodyStyle = doc.defaultView.getComputedStyle(doc.body)
    return bodyStyle.backgroundColor === 'rgba(0, 0, 0, 0)'
    && bodyStyle.backgroundImage === 'none'
        ? doc.defaultView.getComputedStyle(doc.documentElement).background
        : bodyStyle.background
}

/**
 * 创建边距元素数组
 * @param {number} length 数组长度
 * @param {string} part 元素的部分属性值
 * @returns {Array} 返回一个包含边距元素的数组
 */
const makeMarginals = (length, part) => Array.from({length}, () => {
    const div = document.createElement('div')
    const child = document.createElement('div')
    div.append(child)
    child.setAttribute('part', part)
    return div
})

/**
 * 设置元素的样式，并标记为重要
 * @param {Element} el 元素对象
 * @param {Object} styles 样式对象
 */
const setStylesImportant = (el, styles) => {
    const {style} = el
    for (const [k, v] of Object.entries(styles)) style.setProperty(k, v, 'important')
}


/**
 * View 类用于在 iframe 中显示和布局网页内容。
 */
class View {
    // 私有字段用于存储实例变量
    #observer = new ResizeObserver(() => this.expand())
    #element = document.createElement('div')
    #iframe = document.createElement('iframe')
    #contentRange = document.createRange()
    #overlayer
    #vertical = false
    #rtl = false
    #column = true
    #size
    #layout = {}

    /**
     * 构造函数，初始化视图实例，接收容器和扩展回调函数。
     * @param {Object} options - 初始化选项
     * @param {HTMLElement} options.container - 容器元素
     * @param {Function} options.onExpand - 扩展时的回调函数
     */
    constructor({container, onExpand}) {
        this.container = container
        this.onExpand = onExpand
        this.#iframe.setAttribute('part', 'filter')
        this.#element.append(this.#iframe)
        Object.assign(this.#element.style, {
            boxSizing: 'content-box',
            position: 'relative',
            overflow: 'hidden',
            flex: '0 0 auto',
            width: '100%', height: '100%',
            display: 'flex',
            justifyContent: 'center',
            alignItems: 'center',
        })
        Object.assign(this.#iframe.style, {
            overflow: 'hidden',
            border: '0',
            display: 'none',
            width: '100%', height: '100%',
        })
        // `allow-scripts` 是因为 WebKit 的 bug 需要的
        // https://bugs.webkit.org/show_bug.cgi?id=218086
        this.#iframe.setAttribute('sandbox', 'allow-same-origin allow-scripts')
        this.#iframe.setAttribute('scrolling', 'no')
    }

    /**
     * 获取 element 属性
     * @returns {HTMLElement} - 返回元素
     */
    get element() {
        return this.#element
    }

    /**
     * 获取 document 属性，返回 iframe 的内容文档
     * @returns {Document} - 返回 iframe 的内容文档
     */
    get document() {
        return this.#iframe.contentDocument
    }

    /**
     * 异步加载页面到 iframe 并在渲染前进行处理
     * @param {string} src - 页面源地址
     * @param {Function} afterLoad - 加载后的回调函数
     * @param {Function} beforeRender - 渲染前的回调函数
     * @returns {Promise} - 返回一个 Promise，在加载完成后解析
     */
    async load(src, afterLoad, beforeRender) {
        if (typeof src !== 'string') throw new Error(`${src} is not string`)
        return new Promise(resolve => {
            this.#iframe.addEventListener('load', () => {
                const doc = this.document
                afterLoad?.(doc)

                // Firefox 需要可见才能获取计算样式
                this.#iframe.style.display = 'block'
                const {vertical, rtl} = getDirection(doc)
                const background = getBackground(doc)
                this.#iframe.style.display = 'none'

                this.#vertical = vertical
                this.#rtl = rtl

                this.#contentRange.selectNodeContents(doc.body)
                const layout = beforeRender?.({vertical, rtl, background})
                this.#iframe.style.display = 'block'
                this.render(layout)
                this.#observer.observe(doc.body)

                // 上面的 resize observer 在 Firefox 中不起作用
                // (参见 https://bugzilla.mozilla.org/show_bug.cgi?id=1832939)
                // 在修复之前，至少可以考虑字体加载
                doc.fonts.ready.then(() => this.expand())

                resolve()
            }, {once: true})
            this.#iframe.src = src
        })
    }

    /**
     * 根据指定的布局渲染页面
     * @param {Object} layout - 布局参数
     */
    render(layout) {
        if (!layout) return
        this.#column = layout.flow !== 'scrolled'
        this.#layout = layout
        if (this.#column) this.columnize(layout)
        else this.scrolled(layout)
    }

    /**
     * 处理页面在滚动模式下的布局
     * @param {Object} options - 布局参数
     * @param {number} options.gap - 间距
     * @param {number} options.columnWidth - 列宽
     */
    scrolled({gap, columnWidth}) {
        const vertical = this.#vertical
        const doc = this.document
        if (!doc) return
        setStylesImportant(doc.documentElement, {
            'box-sizing': 'border-box',
            'padding': vertical ? `${gap}px 0` : `0 ${gap}px`,
            'column-width': 'auto',
            'height': 'auto',
            'width': 'auto',
        })
        setStylesImportant(doc.body, {
            [vertical ? 'max-height' : 'max-width']: `${columnWidth}px`,
            'margin': 'auto',
        })
        this.setImageSize()
        this.expand()
    }

    /**
     * 处理页面在分栏模式下的布局
     * @param {Object} options - 布局参数
     * @param {number} options.width - 宽度
     * @param {number} options.height - 高度
     * @param {number} options.gap - 间距
     * @param {number} options.columnWidth - 列宽
     */
    columnize({width, height, gap, columnWidth}) {
        const vertical = this.#vertical
        this.#size = vertical ? height : width

        const doc = this.document
        setStylesImportant(doc.documentElement, {
            'box-sizing': 'border-box',
            'column-width': `${Math.trunc(columnWidth)}px`,
            'column-gap': `${gap}px`,
            'column-fill': 'auto',
            ...(vertical
                ? {'width': `${width}px`}
                : {'height': `${height}px`}),
            'padding': vertical ? `${gap / 2}px 0` : `0 ${gap / 2}px`,
            'overflow': 'hidden',
            // 强制换行长单词
            'overflow-wrap': 'break-word',
            // 重置一些可能有问题的属性
            'position': 'static', 'border': '0', 'margin': '0',
            'max-height': 'none', 'max-width': 'none',
            'min-height': 'none', 'min-width': 'none',
            // 修复 WebKit 中的字形裁剪
            '-webkit-line-box-contain': 'block glyphs replaced',
        })
        setStylesImportant(doc.body, {
            'max-height': 'none',
            'max-width': 'none',
            'margin': '0',
        })
        this.setImageSize()
        this.expand()
    }

    /**
     * 设置图像大小
     */
    setImageSize() {
        const {width, height, margin} = this.#layout
        const vertical = this.#vertical
        const doc = this.document
        for (const el of doc.body.querySelectorAll('img, svg, video')) {
            // 如果已经设置了最大尺寸，则保留
            const {maxHeight, maxWidth} = doc.defaultView.getComputedStyle(el)
            setStylesImportant(el, {
                'max-height': vertical
                    ? (maxHeight !== 'none' && maxHeight !== '0px' ? maxHeight : '100%')
                    : `${height - margin * 2}px`,
                'max-width': vertical
                    ? `${width - margin * 2}px`
                    : (maxWidth !== 'none' && maxWidth !== '0px' ? maxWidth : '100%'),
                'object-fit': 'contain',
                'page-break-inside': 'avoid',
                'break-inside': 'avoid',
                'box-sizing': 'border-box',
            })
        }
    }

    /**
     * 扩展视图以适应内容
     */
    expand() {
        const {documentElement} = this.document
        if (this.#column) {
            const side = this.#vertical ? 'height' : 'width'
            const otherSide = this.#vertical ? 'width' : 'height'
            this.#contentRange.selectNodeContents(this.document.body)
            const contentRect = this.#contentRange.getBoundingClientRect()
            const rootRect = documentElement.getBoundingClientRect()
            // 由于页面开头的列中断导致的偏移
            // 这似乎只被 WebKit 支持，并且只适用于水平书写
            const contentStart = this.#vertical ? 0
                : this.#rtl ? rootRect.right - contentRect.right : contentRect.left - rootRect.left
            const contentSize = contentStart + contentRect[side]
            const pageCount = Math.ceil(contentSize / this.#size)
            const expandedSize = pageCount * this.#size
            this.#element.style.padding = '0'
            this.#iframe.style[side] = `${expandedSize}px`
            this.#element.style[side] = `${expandedSize + this.#size * 2}px`
            this.#iframe.style[otherSide] = '100%'
            this.#element.style[otherSide] = '100%'
            documentElement.style[side] = `${this.#size}px`
            if (this.#overlayer) {
                this.#overlayer.element.style.margin = '0'
                this.#overlayer.element.style.left = this.#vertical ? '0' : `${this.#size}px`
                this.#overlayer.element.style.top = this.#vertical ? `${this.#size}px` : '0'
                this.#overlayer.element.style[side] = `${expandedSize}px`
                this.#overlayer.redraw()
            }
        } else {
            const side = this.#vertical ? 'width' : 'height'
            const otherSide = this.#vertical ? 'height' : 'width'
            const contentSize = documentElement.getBoundingClientRect()[side]
            const expandedSize = contentSize
            const {margin} = this.#layout
            const padding = this.#vertical ? `0 ${margin}px` : `${margin}px 0`
            this.#element.style.padding = padding
            this.#iframe.style[side] = `${expandedSize}px`
            this.#element.style[side] = `${expandedSize}px`
            this.#iframe.style[otherSide] = '100%'
            this.#element.style[otherSide] = '100%'
            if (this.#overlayer) {
                this.#overlayer.element.style.margin = padding
                this.#overlayer.element.style.left = '0'
                this.#overlayer.element.style.top = '0'
                this.#overlayer.element.style[side] = `${expandedSize}px`
                this.#overlayer.redraw()
            }
        }
        this.onExpand()
    }

    /**
     * 设置覆盖层
     * @param {Object} overlayer - 覆盖层对象
     */
    set overlayer(overlayer) {
        this.#overlayer = overlayer
        this.#element.append(overlayer.element)
    }

    /**
     * 获取覆盖层
     * @returns {Object} - 返回覆盖层对象
     */
    get overlayer() {
        return this.#overlayer
    }

    /**
     * 销毁视图实例
     */
    destroy() {
        if (this.document) this.#observer.unobserve(this.document.body)
    }
}

// NOTE: everything here assumes the so-called "negative scroll type" for RTL
export class Paginator extends HTMLElement {
    static observedAttributes = [
        'flow', 'gap', 'top-margin', 'bottom-margin', 'background-color',
        'max-inline-size', 'max-block-size', 'max-column-count',
    ]
    // 创建一个 Shadow DOM 根节点，模式为 'open'，允许外部访问其内容
    #root = this.attachShadow({mode: 'open'})

// 创建一个 ResizeObserver 实例，当 #container 大小发生变化时调用 render 方法
    #observer = new ResizeObserver(() => this.render())

// 存储顶部元素的引用
    #top

// 存储背景元素的引用
    #background

// 存储容器元素的引用
    #container

// 注释掉的头部元素引用，当前未使用
// #header

// 注释掉的底部元素引用，当前未使用
// #footer

// 存储视图实例的引用
    #view

// 标志位，表示是否为垂直布局，默认为 false
    #vertical = false

// 标志位，表示是否为从右到左的布局，默认为 false
    #rtl = false

// 存储外边距值，默认为 0
    #margin = 0

// 存储当前章节的索引，默认为 -1
    #index = -1

// 存储锚点信息，可以是小数（0-1）、Range 对象或 Element 对象，默认为 0
    #anchor = 0

// 标志位，表示视图是否刚刚被锚定，默认为 false
    #justAnchored = false

// 标志位，当为 true 时阻止任何进一步的导航，默认为 false
    #locked = false

// 存储样式信息
    #styles

// 使用 WeakMap 存储样式信息与元素的关联
    #styleMap = new WeakMap()

// 媒体查询对象，用于检测系统是否偏好暗色模式
    #mediaQuery = matchMedia('(prefers-color-scheme: dark)')

// 媒体查询变化事件的监听器
    #mediaQueryListener

// 存储滚动边界信息
    #scrollBounds

// 存储触摸交互状态信息
    #touchState

// 标志位，表示是否在触摸交互中发生了滚动，默认为 false
    #touchScrolled

    /**
     * 构造函数
     * 初始化元素，设置样式和事件监听器
     */
    constructor() {
        super()
        // 设置元素的innerHTML，包括样式和基本结构
        this.#root.innerHTML = `<style>
        :host {
            display: block;
            container-type: size;
        }
        :host, #top {
            box-sizing: border-box;
            position: relative;
            overflow: hidden;
            width: 100%;
            height: 100%;
        }
        #top {
            height: 100%;
            // --_gap: 7%;
            background-color: var(--_background-color);
            --_max-inline-size: 720px;
            --_max-block-size: 1440px;
            --_max-column-count: 2;
            --_max-column-count-portrait: 1;
            --_max-column-count-spread: var(--_max-column-count);
            --_half-gap: calc(var(--_gap) / 2);
            --_max-width: calc(var(--_max-inline-size) * var(--_max-column-count-spread));
            --_max-height: var(--_max-block-size);
            display: grid;
            grid-template-columns:
                minmax(var(--_half-gap), 1fr)
                var(--_half-gap)
                minmax(0, calc(var(--_max-width) - var(--_gap)))
                var(--_half-gap)
                minmax(var(--_half-gap), 1fr);
            grid-template-rows:
                var(--_top-margin)
                1fr
                var(--_bottom-margin);
            &.vertical {
                --_max-column-count-spread: var(--_max-column-count-portrait);
                --_max-width: var(--_max-block-size);
                --_max-height: calc(var(--_max-inline-size) * var(--_max-column-count-spread));
            }
            @container (orientation: portrait) {
                & {
                    --_max-column-count-spread: var(--_max-column-count-portrait);
                }
                &.vertical {
                    --_max-column-count-spread: var(--_max-column-count);
                }
            }
        }
        #background {
            grid-column: 1 / -1;
            grid-row: 1 / -1;
        }
        #container {
            grid-column: 1 / -1;
            grid-row: 2;
            overflow: hidden;
        }
        :host([flow="scrolled"]) #container {
            grid-column: 1 / -1;
            grid-row: 2;
            overflow: auto;
        }
        #header {
            grid-column: 3 / 4;
            grid-row: 1;
        }
        #footer {
            grid-column: 3 / 4;
            grid-row: 3;
            align-self: end;
        }
        #header, #footer {
            display: grid;
            height: var(--_margin);
        }
        :is(#header, #footer) > * {
            display: flex;
            align-items: center;
            min-width: 0;
        }
        :is(#header, #footer) > * > * {
            width: 100%;
            overflow: hidden;
            white-space: nowrap;
            text-overflow: ellipsis;
            text-align: center;
            font-size: .75em;
            opacity: .6;
        }
        </style>
        <div id="top">
            <div id="background" part="filter"></div>
            <div id="container"></div>
        </div>
        `

        // 获取元素引用
        this.#top = this.#root.getElementById('top')
        this.#background = this.#root.getElementById('background')
        this.#container = this.#root.getElementById('container')
        // this.#header = this.#root.getElementById('header')
        // this.#footer = this.#root.getElementById('footer')

        // 设置滚动事件监听器
        this.#observer.observe(this.#container)
        this.#container.addEventListener('scroll', debounce(() => {
            if (this.scrolled) {
                console.log("zdl","========paginator=========设置滚动事件监听器====714=====");
                if (this.#justAnchored){
                    console.log("zdl","========paginator=========设置滚动事件监听器====726====1=");
                    this.#justAnchored = false
                    //这里进行翻页
                    // this.#afterScroll('scroll')
                    // this.next(1);
                    // this.goTo()
                } else {
                    console.log("zdl","========paginator=========设置滚动事件监听器====719====1=");
                    this.#afterScroll('scroll')
                }
            }
        }, 50))

        // 设置触摸事件监听器
        const opts = {passive: false}
        this.addEventListener('touchstart', this.#onTouchStart.bind(this), opts)
        this.addEventListener('touchmove', this.#onTouchMove.bind(this), opts)
        this.addEventListener('touchend', this.#onTouchEnd.bind(this))
        this.addEventListener('load', ({detail: {doc}}) => {
            doc.addEventListener('touchstart', this.#onTouchStart.bind(this), opts)
            doc.addEventListener('touchmove', this.#onTouchMove.bind(this), opts)
            doc.addEventListener('touchend', this.#onTouchEnd.bind(this))
        })

        // 设置媒体查询事件监听器
        this.#mediaQueryListener = () => {
            if (!this.#view) return
            this.#background.style.background = getBackground(this.#view.document)
        }
        this.#mediaQuery.addEventListener('change', this.#mediaQueryListener)
    }

    /**
     * 属性改变时的回调函数
     * 根据不同的属性进行相应的处理
     * @param {string} name - 属性名
     * @param {string} _ - 旧的属性值
     * @param {string} value - 新的属性值
     */
    attributeChangedCallback(name, _, value) {
        switch (name) {
            case 'flow':
                this.render()
                break
            case 'top-margin':
            case 'max-block-size':
            case 'background-color':
                this.#top.style.setProperty('--_' + name, value)
                break
            case 'bottom-margin':
            case 'gap':
            case 'max-column-count':
            case 'max-inline-size':
                // needs explicit `render()` as it doesn't necessarily resize
                this.#top.style.setProperty('--_' + name, value)
                this.render()
                break
        }
    }

    /**
     * 打开一本书并设置其目录和章节。
     * @param {Object} book - 包含目录和章节属性的书对象。
     * @param {string} book.dir - 书的目录。
     * @param {Array} book.sections - 书的章节列表。
     */
    open(book) {
        this.bookDir = book.dir;
        this.sections = book.sections;
    }

    /**
     * 创建并返回一个新的视图对象。
     * 如果已经存在一个视图，则会先销毁并移除它，然后再创建新的视图。
     * @returns {View} 新创建的视图对象。
     */
    #createView() {
        if (this.#view) {
            // 销毁当前视图并从容器中移除其元素
            this.#view.destroy();
            this.#container.removeChild(this.#view.element);
        }
        // 创建新的视图对象，并在扩展时滚动到锚点
        this.#view = new View({
            container: this,
            onExpand: () => this.scrollToAnchor(this.#anchor),
        });
        // 将新视图的元素添加到容器中
        this.#container.append(this.#view.element);
        return this.#view;
    }

    /**
     * 在渲染之前进行一些预处理操作。
     * 设置视图的方向、背景颜色等属性，并计算布局参数。
     * @param {Object} options - 包含垂直方向、RTL方向和背景颜色的对象。
     * @param {boolean} options.vertical - 是否为垂直方向。
     * @param {boolean} options.rtl - 是否为从右到左的方向。
     * @param {string} options.background - 背景颜色。
     * @returns {Object} 包含布局参数的对象，如流类型、边距、间隙、列宽等。
     */
    #beforeRender({vertical, rtl, background}) {
        this.#vertical = vertical;
        this.#rtl = rtl;
        // 切换顶部容器的垂直方向类
        this.#top.classList.toggle('vertical', vertical);

        // 设置背景颜色为文档的背景颜色
        this.#background.style.background = background;

        const {width, height} = this.#container.getBoundingClientRect();
        const size = vertical ? height : width;

        const style = getComputedStyle(this.#top);
        const maxInlineSize = parseFloat(style.getPropertyValue('--_max-inline-size'));
        const maxColumnCount = parseInt(style.getPropertyValue('--_max-column-count'));
        const margin = parseFloat(style.getPropertyValue('--_top-margin'));
        this.#margin = margin;

        const g = parseFloat(style.getPropertyValue('--_gap')) / 100;
        // 计算间隙大小，使其与外边距一致
        const gap = -g / (g - 1) * size;

        const flow = this.getAttribute('flow');
        if (flow === 'scrolled') {
            // 设置方向属性为RTL或LTR
            this.setAttribute('dir', vertical ? 'rtl' : 'ltr');
            this.#top.style.padding = '0';
            const columnWidth = maxInlineSize;

            this.heads = null;
            this.feet = null;

            return {flow, margin, gap, columnWidth};
        }

        const divisor = maxColumnCount == 0
            ? Math.min(2, Math.ceil(size / maxInlineSize))
            : maxColumnCount;

        const columnWidth = (size / divisor) - gap;
        this.setAttribute('dir', rtl ? 'rtl' : 'ltr');

        const marginalDivisor = vertical
            ? Math.min(2, Math.ceil(width / maxInlineSize))
            : divisor;
        const marginalStyle = {
            gridTemplateColumns: `repeat(${marginalDivisor}, 1fr)`,
            gap: `${gap}px`,
            direction: this.bookDir === 'rtl' ? 'rtl' : 'ltr',
        };

        const heads = makeMarginals(marginalDivisor, 'head');
        const feet = makeMarginals(marginalDivisor, 'foot');
        this.heads = heads.map(el => el.children[0]);
        this.feet = feet.map(el => el.children[0]);

        return {height, width, margin, gap, columnWidth};
    }

    /**
     * 渲染视图。
     * 如果没有视图则直接返回。
     * 使用 #beforeRender 方法计算布局参数并渲染视图。
     * 滚动到指定的锚点位置。
     */
    render() {
        if (!this.#view) return;
        this.#view.render(this.#beforeRender({
            vertical: this.#vertical,
            rtl: this.#rtl,
        }));
        this.scrollToAnchor(this.#anchor);
    }

    /**
     * 获取是否为滚动模式。
     * @returns {boolean} 如果流属性为 'scrolled'，则返回 true，否则返回 false。
     */
    get scrolled() {
        return this.getAttribute('flow') === 'scrolled';
    }

    /**
     * 获取滚动属性名称。
     * 根据垂直方向和滚动模式确定滚动属性名称。
     * @returns {string} 滚动属性名称，如 'scrollLeft' 或 'scrollTop'。
     */
    get scrollProp() {
        const {scrolled} = this;
        return this.#vertical ? (scrolled ? 'scrollLeft' : 'scrollTop')
            : scrolled ? 'scrollTop' : 'scrollLeft';
    }

    /**
     * 获取侧边属性名称。
     * 根据垂直方向和滚动模式确定侧边属性名称。
     * @returns {string} 侧边属性名称，如 'width' 或 'height'。
     */
    get sideProp() {
        const {scrolled} = this;
        return this.#vertical ? (scrolled ? 'width' : 'height')
            : scrolled ? 'height' : 'width';
    }

    /**
     * 获取容器的尺寸。
     * @returns {number} 容器在指定方向上的尺寸。
     */
    get size() {
        return this.#container.getBoundingClientRect()[this.sideProp];
    }

    /**
     * 获取视图的尺寸。
     * @returns {number} 视图在指定方向上的尺寸。
     */
    get viewSize() {
        return this.#view.element.getBoundingClientRect()[this.sideProp];
    }

    /**
     * 获取滚动起始位置。
     * @returns {number} 当前滚动位置的绝对值。
     */
    get start() {
        return Math.abs(this.#container[this.scrollProp]);
    }

    /**
     * 获取滚动结束位置。
     * @returns {number} 当前滚动起始位置加上容器尺寸后的值。
     */
    get end() {
        return this.start + this.size;
    }

    /**
     * 获取当前页码。
     * @returns {number} 当前页面的页码。
     */
    get page() {
        return Math.floor(((this.start + this.end) / 2) / this.size);
    }

    /**
     * 获取总页数。
     * @returns {number} 总页数。
     */
    get pages() {
        return Math.round(this.viewSize / this.size);
    }

    /**
     * 滚动指定的距离。
     * @param {number} dx - 水平方向的滚动距离。
     * @param {number} dy - 垂直方向的滚动距离。
     */
    scrollBy(dx, dy) {
        const delta = this.#vertical ? dy : dx;
        const element = this.#container;
        const {scrollProp} = this;
        const [offset, a, b] = this.#scrollBounds;
        const rtl = this.#rtl;
        const min = rtl ? offset - b : offset - a;
        const max = rtl ? offset + a : offset + b;
        element[scrollProp] = Math.max(min, Math.min(max,
            element[scrollProp] + delta));
    }

    /**
     * 快速滚动到指定页面。
     * @param {number} vx - 水平方向的速度。
     * @param {number} vy - 垂直方向的速度。
     */
    snap(vx, vy) {
        const velocity = this.#vertical ? vy : vx;
        const [offset, a, b] = this.#scrollBounds;
        const {start, end, pages, size} = this;
        const min = Math.abs(offset) - a;
        const max = Math.abs(offset) + b;
        const d = velocity * (this.#rtl ? -size : size);
        const page = Math.floor(
            Math.max(min, Math.min(max, (start + end) / 2
                + (isNaN(d) ? 0 : d))) / size);

        this.#scrollToPage(page, 'snap').then(() => {
            const dir = page <= 0 ? -1 : page >= pages - 1 ? 1 : null;
            if (dir) return this.#goTo({
                index: this.#adjacentIndex(dir),
                anchor: dir < 0 ? () => 1 : () => 0,
            });
        });
    }

    /**
     * 处理触摸开始事件。
     * 初始化触摸状态。
     * @param {TouchEvent} e - 触摸事件对象。
     */
    #onTouchStart(e) {
        const touch = e.changedTouches[0];
        this.#touchState = {
            x: touch?.screenX, y: touch?.screenY,
            t: e.timeStamp,
            vx: 0, xy: 0,
        };
    }

    /**
     * 处理触摸移动事件。
     * 根据触摸移动的距离和时间计算速度，并滚动视图。
     * @param {TouchEvent} e - 触摸事件对象。
     */
    #onTouchMove(e) {
        if (window.getSelection()?.toString()) return;

        const state = this.#touchState;
        if (!state) return;

        if (state.pinched) return;
        state.pinched = globalThis.visualViewport.scale > 1;

        if (this.scrolled || state.pinched) return;

        if (e.touches.length > 1) {
            if (this.#touchScrolled) e.preventDefault();
            return;
        }

        e.preventDefault();

        if (!state.animationFrameId) {
            state.animationFrameId = requestAnimationFrame(() => {
                const touch = e.changedTouches[0];
                const x = touch.screenX, y = touch.screenY;

                const dx = state.x - x, dy = state.y - y;
                const dt = e.timeStamp - state.t || 16.7;

                const damping = 0.85;
                const smoothDx = dx * damping;
                const smoothDy = dy * damping;

                state.x = x;
                state.y = y;
                state.t = e.timeStamp;
                state.vx = dx / dt;
                state.vy = dy / dt;

                this.#touchScrolled = true;
                this.scrollBy(smoothDx, smoothDy);

                state.animationFrameId = null;
            });
        }
    }

    /**
     * 处理触摸结束事件，判断是否需要执行 snap 操作。
     * 如果已经发生了滚动，则不执行任何操作。
     */
    #onTouchEnd() {
        this.#touchScrolled = false;
        if (this.scrolled) return;

        // 使用 requestAnimationFrame 解决 Firefox 可能错误报告缩放比例的问题
        requestAnimationFrame(() => {
            if (globalThis.visualViewport.scale === 1)
                this.snap(this.#touchState.vx, this.#touchState.vy);
        });
    }

    /**
     * 获取一个矩形映射器，用于将矩形坐标转换为 LTR 和水平方向的坐标。
     * @returns {function} - 返回一个函数，用于处理矩形坐标的映射。
     */
    #getRectMapper() {
        if (this.scrolled) {
            const size = this.viewSize;
            const margin = this.#margin;
            return this.#vertical
                ? ({left, right}) => ({left: size - right - margin, right: size - left - margin})
                : ({top, bottom}) => ({left: top + margin, right: bottom + margin});
        }
        const pxSize = this.pages * this.size;
        return this.#rtl
            ? ({left, right}) => ({left: pxSize - right, right: pxSize - left})
            : this.#vertical
                ? ({top, bottom}) => ({left: top, right: bottom})
                : f => f;
    }

    /**
     * 滚动到指定的矩形区域。
     * @param {object} rect - 目标矩形区域。
     * @param {string} reason - 滚动原因。
     * @returns {Promise<void>} - 返回一个 Promise，表示滚动操作完成。
     */
    async #scrollToRect(rect, reason) {

        console.log('zdl', "================scrollToRect=========1107======");
        if (this.scrolled) {
            const offset = this.#getRectMapper()(rect).left - this.#margin;
            return this.#scrollTo(offset, reason);
        }
        const offset = this.#getRectMapper()(rect).left + this.#margin / 2;
        return this.#scrollToPage(Math.floor(offset / this.size) + (this.#rtl ? -1 : 1), reason);
    }

    /**
     * 滚动到指定的偏移位置。
     * @param {number} offset - 滚动的目标偏移量。
     * @param {string} reason - 滚动原因。
     * @param {boolean} smooth - 是否启用平滑滚动。
     * @returns {Promise<void>} - 返回一个 Promise，表示滚动操作完成。
     */
    async #scrollTo(offset, reason, smooth) {
        const element = this.#container;
        const {scrollProp, size} = this;
        console.log('zdl', "滚动的目标偏移量offset:" + offset, "滚动原因reason:" + reason, "是否启用平滑滚动:" + smooth);

        if (Math.abs(element[scrollProp] - offset) < 1) {
            this.#scrollBounds = [offset, this.atStart ? 0 : size, this.atEnd ? 0 : size];
            console.log('zdl', "================滚动完后执行=========1131======");
            this.#afterScroll(reason);
            return;
        }

        if (this.scrolled && this.#vertical) offset = -offset;

        const useAnimation = (reason === 'snap' || smooth) && this.hasAttribute('animated');

        if (useAnimation) {
            const distance = Math.abs(element[scrollProp] - offset);
            const baseDuration = 300;
            const adaptiveDuration = Math.min(
                500,
                Math.max(200, baseDuration * (distance / size))
            );

            const smoothEasing = x => 1 - Math.pow(1 - x, 3);

            this.#justAnchored = true;

            return animate(
                element[scrollProp],
                offset,
                adaptiveDuration,
                smoothEasing,
                x => element[scrollProp] = x,
            ).then(() => {
                element[scrollProp] = offset;
                this.#scrollBounds = [offset, this.atStart ? 0 : size, this.atEnd ? 0 : size];

                return wait(10).then(() => {
                    console.log('zdl', "================滚动完后执行=========1162======");
                    this.#afterScroll(reason);
                });
            });
        } else {
            element[scrollProp] = offset;
            this.#scrollBounds = [offset, this.atStart ? 0 : size, this.atEnd ? 0 : size];
            console.log('zdl', "================滚动完后执行=========1168======");
            this.#afterScroll(reason);
        }
    }

    /**
     * 滚动到指定的页码。
     * @param {number} page - 目标页码。
     * @param {string} reason - 滚动原因。
     * @param {boolean} smooth - 是否启用平滑滚动。
     * @returns {Promise<void>} - 返回一个 Promise，表示滚动操作完成。
     */
    async #scrollToPage(page, reason, smooth) {
        console.log('zdl', "================scrollToPage=========1179======");
        const offset = this.size * (this.#rtl ? -page : page);
        return this.#scrollTo(offset, reason, smooth);
    }

    /**
     * 滚动到指定的锚点。
     * @param {any} anchor - 锚点信息，可以是元素、范围或分数。
     * @param {boolean} select - 是否选择锚点。
     * @returns {Promise<void>} - 返回一个 Promise，表示滚动操作完成。
     */
    async scrollToAnchor(anchor, select) {
        console.log('zdl', "================scrollToAnchor=========1191======");
        this.#anchor = anchor;
        const rects = uncollapse(anchor)?.getClientRects?.();

        if (rects) {
            const rect = Array.from(rects)
                .find(r => r.width > 0 && r.height > 0) || rects[0];
            if (!rect) return;
            await this.#scrollToRect(rect, 'anchor');
            if (select) this.#selectAnchor();
            return;
        }

        if (this.scrolled) {
            await this.#scrollTo(anchor * this.viewSize, 'anchor');
            return;
        }

        const {pages} = this;
        if (!pages) return;
        const textPages = pages - 2;
        const newPage = Math.round(anchor * (textPages - 1));
        await this.#scrollToPage(newPage + 1, 'anchor');
    }

    /**
     * 选择锚点。
     */
    #selectAnchor() {
        const {defaultView} = this.#view.document;
        if (this.#anchor.startContainer) {
            const sel = defaultView.getSelection();
            sel.removeAllRanges();
            sel.addRange(this.#anchor);
        }
    }

    /**
     * 获取当前可见的范围。
     * @returns {Range|null} - 返回当前可见的范围。
     */
    #getVisibleRange() {
        if (this.scrolled) return getVisibleRange(this.#view.document,
            this.start + this.#margin, this.end - this.#margin, this.#getRectMapper());
        const size = this.#rtl ? -this.size : this.size;
        return getVisibleRange(this.#view.document,
            this.start - size, this.end - size, this.#getRectMapper());
    }

    /**
     * 滚动完成后执行的操作。
     * @param {string} reason - 滚动原因。
     */
    #afterScroll(reason) {
        const range = this.#getVisibleRange();
        if (reason !== 'anchor') this.#anchor = range;
        else this.#justAnchored = true;
        const index = this.#index;
        const detail = {reason, range, index};
        if (this.scrolled) detail.fraction = this.start / this.viewSize;
        else if (this.pages > 0) {
            const {page, pages} = this;
            detail.fraction = (page - 1) / (pages - 2);
            detail.size = 1 / (pages - 2);
        }
        console.log('zdl', "================afterScroll====滚动完成后执行的操作=====1262=====reason="+reason);
        this.dispatchEvent(new CustomEvent('relocate', {detail}));
    }

    /**
     * 异步显示内容。
     * 根据传入的 promise，加载并显示相关内容，包括创建视图、设置样式和滚动到指定位置。
     * @param {Promise} promise - 包含内容信息的 Promise 对象。
     */
    async #display(promise) {
        // 解构 promise 结果，获取索引、资源路径、锚点等信息
        const {index, src, anchor, onLoad, select} = await promise;
        this.#index = index;

        if (src) {
            // 如果存在资源路径，则创建视图
            const view = this.#createView();

            // 加载完成后执行的回调函数
            const afterLoad = doc => {
                if (doc.head) {
                    // 在文档头部插入两个 style 元素，并存储到样式映射中
                    const $styleBefore = doc.createElement('style');
                    doc.head.prepend($styleBefore);
                    const $style = doc.createElement('style');
                    doc.head.append($style);
                    this.#styleMap.set(doc, [$styleBefore, $style]);
                }
                // 调用加载完成回调函数
                onLoad?.({doc, index});
            };

            // 绑定渲染前的回调函数
            const beforeRender = this.#beforeRender.bind(this);

            // 加载视图内容
            await view.load(src, afterLoad, beforeRender);

            // 触发自定义事件，通知创建覆盖层
            this.dispatchEvent(new CustomEvent('create-overlayer', {
                detail: {
                    doc: view.document,
                    index,
                    attach: overlayer => view.overlayer = overlayer,
                },
            }));

            // 设置当前视图为新创建的视图
            this.#view = view;
        }

        // 滚动到指定的锚点位置
        await this.scrollToAnchor((typeof anchor === 'function'
            ? anchor(this.#view.document) : anchor) ?? 0, select);
    }

    /**
     * 判断是否可以跳转到指定索引。
     * @param {number} index - 目标索引。
     * @returns {boolean} - 如果索引有效则返回 true，否则返回 false。
     */
    #canGoToIndex(index) {
        return index >= 0 && index <= this.sections.length - 1;
    }

    /**
     * 跳转到指定索引的内容。
     * @param {Object} options - 跳转选项，包含索引、锚点和选择信息。
     */
    async #goTo({index, anchor, select}) {
        console.log("zdl","====paginator======跳转到指定索引的内容===1332=goTo=====index="+index);
        if (index === this.#index) {
            // 如果目标索引与当前索引相同，则直接显示内容
            await this.#display({index, anchor, select});
        } else {
            const oldIndex = this.#index;

            // 定义加载完成后的回调函数
            const onLoad = detail => {
                this.sections[oldIndex]?.unload?.(); // 卸载旧章节
                this.setStyles(this.#styles); // 设置样式
                this.dispatchEvent(new CustomEvent('load', {detail})); // 触发加载事件
            };

            // 显示目标索引的内容
            await this.#display(Promise.resolve(this.sections[index].load())
                .then(src => ({index, src, anchor, onLoad, select}))
                .catch(e => {
                    console.warn(e);
                    console.warn(new Error(`Failed to load section ${index}`));
                    return {};
                }));
        }
    }

    /**
     * 跳转到目标内容。
     * @param {Object|Promise} target - 目标内容或返回目标内容的 Promise。
     */
    async goTo(target) {
        if (this.#locked) return; // 如果被锁定，则不允许跳转

        const resolved = await target; // 等待目标内容解析完成

        // 如果索引有效，则跳转到目标内容
        if (this.#canGoToIndex(resolved.index)) return this.#goTo(resolved);
    }

    /**
     * 滚动到上一页。
     * @param {number} distance - 滚动距离。
     * @returns {Promise<boolean>} - 返回是否到达第一页。
     */
    #scrollPrev(distance) {
        if (!this.#view) return true; // 如果没有视图，则直接返回 true

        if (this.scrolled) {
            // 如果是滚动模式且起始位置大于 0，则滚动到上一页
            if (this.start > 0) return this.#scrollTo(
                Math.max(0, this.start - (distance ?? this.size)), null, true);
            return true;
        }

        if (this.atStart) return; // 如果已经在第一页，则不操作

        const page = this.page - 1; // 计算目标页码
        return this.#scrollToPage(page, 'page', true).then(() => page <= 0);
    }

    /**
     * 滚动到下一页。
     * @param {number} distance - 滚动距离。
     * @returns {Promise<boolean>} - 返回是否到达最后一页。
     */
    #scrollNext(distance) {
        if (!this.#view) return true; // 如果没有视图，则直接返回 true

        if (this.scrolled) {
            // 如果是滚动模式且未到达最后一页，则滚动到下一页
            if (this.viewSize - this.end > 2) return this.#scrollTo(
                Math.min(this.viewSize, distance ? this.start + distance : this.end), null, true);
            return true;
        }

        if (this.atEnd) return; // 如果已经在最后一页，则不操作

        const page = this.page + 1; // 计算目标页码
        const pages = this.pages;
        return this.#scrollToPage(page, 'page', true).then(() => page >= pages - 1);
    }

    /**
     * 判断是否在第一页。
     * @returns {boolean} - 如果在第一页则返回 true，否则返回 false。
     */
    get atStart() {
        return this.#adjacentIndex(-1) == null && this.page <= 1;
    }

    /**
     * 判断是否在最后一页。
     * @returns {boolean} - 如果在最后一页则返回 true，否则返回 false。
     */
    get atEnd() {
        return this.#adjacentIndex(1) == null && this.page >= this.pages - 2;
    }

    /**
     * 获取相邻索引。
     * @param {number} dir - 方向，-1 表示向前，1 表示向后。
     * @returns {number|null} - 返回相邻索引，如果不存在则返回 null。
     */
    #adjacentIndex(dir) {
        for (let index = this.#index + dir; this.#canGoToIndex(index); index += dir)
            if (this.sections[index]?.linear !== 'no') return index;
    }

    /**
     * 翻页。
     * @param {number} dir - 方向，-1 表示向前翻页，1 表示向后翻页。
     * @param {number} distance - 翻页距离。
     */
    async #turnPage(dir, distance) {
        this.#locked = true; // 锁定翻页操作
        const prev = dir === -1; // 判断是否向前翻页

        // 滚动到上一页或下一页
        const shouldGo = await (prev ? this.#scrollPrev(distance) : this.#scrollNext(distance));
        console.log("zdl","====paginator======翻页=====turnPage==1449=shouldGo="+shouldGo);
        if (shouldGo) {
            // 如果需要翻页，则跳转到相邻索引
            await this.#goTo({
                index: this.#adjacentIndex(dir),
                anchor: prev ? () => 1 : () => 0,
            });
        }

        // 等待动画完成或解锁翻页操作
        if (shouldGo || !this.hasAttribute('animated')) await wait(100);
        this.#locked = false;
    }

    /**
     * 翻到上一页
     * @param {number} distance - 翻页的距离
     * @returns {any} - 返回调用私有方法 #turnPage 的结果
     */
    prev(distance) {
        return this.#turnPage(-1, distance)
    }

    /**
     * 翻到下一页
     * @param {number} distance - 翻页的距离
     * @returns {any} - 返回调用私有方法 #turnPage 的结果
     */
    next(distance) {
        console.log("zdl","====paginator======翻页=====next==1478=");
        return this.#turnPage(1, distance)
    }

    /**
     * 跳转到前一个章节
     * @returns {any} - 返回调用 goTo 方法的结果
     */
    prevSection() {
        console.log("zdl","====paginator======跳转到前一个章节=====prevSection==1487=");
        return this.goTo({index: this.#adjacentIndex(-1)})
    }

    /**
     * 跳转到后一个章节
     * @returns {any} - 返回调用 goTo 方法的结果
     */
    nextSection() {
        console.log("zdl","====paginator======跳转到前一个章节=====nextSection==1496=");
        return this.goTo({index: this.#adjacentIndex(1)})
    }

    /**
     * 跳转到第一个章节
     * @returns {any} - 返回调用 goTo 方法的结果
     */
    firstSection() {
        const index = this.sections.findIndex(section => section.linear !== 'no')
        return this.goTo({index})
    }

    /**
     * 跳转到最后一个章节
     * @returns {any} - 返回调用 goTo 方法的结果
     */
    lastSection() {
        const index = this.sections.findLastIndex(section => section.linear !== 'no')
        return this.goTo({index})
    }

    /**
     * 获取当前视图的内容
     * @returns {array|*} - 如果有视图则返回包含视图信息的数组，否则返回空数组
     */
    getContents() {
        if (this.#view) return [{
            index: this.#index,
            overlayer: this.#view.overlayer,
            doc: this.#view.document,
        }]
        return []
    }

    /**
     * 设置样式
     * @param {array|string} styles - 要设置的样式，可以是数组或字符串
     */
    setStyles(styles) {
        this.#styles = styles
        const $$styles = this.#styleMap.get(this.#view?.document)
        if (!$$styles) return
        const [$beforeStyle, $style] = $$styles
        if (Array.isArray(styles)) {
            const [beforeStyle, style] = styles
            $beforeStyle.textContent = beforeStyle
            $style.textContent = style
        } else $style.textContent = styles

        this.#background.style.background = getBackground(this.#view.document)

        // needed because the resize observer doesn't work in Firefox
        this.#view?.document?.fonts?.ready?.then(() => this.#view.expand())
    }

    /**
     * 销毁实例
     * 从观察者中移除自身，销毁视图，卸载当前章节，并移除事件监听器
     */
    destroy() {
        this.#observer.unobserve(this)
        this.#view.destroy()
        this.#view = null
        this.sections[this.#index]?.unload?.()
        this.#mediaQuery.removeEventListener('change', this.#mediaQueryListener)
    }
}

customElements.define('foliate-paginator', Paginator)
